home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / JFC.bin / PlainView.java < prev    next >
Text File  |  1998-06-30  |  19KB  |  510 lines

  1. /*
  2.  * @(#)PlainView.java    1.48 98/04/09
  3.  * 
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  */
  20. package com.sun.java.swing.text;
  21.  
  22. import java.util.Vector;
  23. import java.util.Properties;
  24. import java.awt.*;
  25. import com.sun.java.swing.event.*;
  26.  
  27. /**
  28.  * Implements View interface for a simple multi-line text view 
  29.  * that has text in one font and color.  The view represents each
  30.  * child element as a line of text.
  31.  *
  32.  * @author  Timothy Prinzing
  33.  * @version 1.48 04/09/98
  34.  * @see     View
  35.  */
  36. public class PlainView extends View implements TabExpander {
  37.  
  38.     /**
  39.      * Constructs a new PlainView wrapped on an element.
  40.      *
  41.      * @param elem the element
  42.      */
  43.     public PlainView(Element elem) {
  44.         super(elem);
  45.         lineBuffer = new Segment();
  46.     }
  47.  
  48.     /**
  49.      * Returns the tab size set for the document, defaulting to 8.
  50.      *
  51.      * @return the tab size
  52.      */
  53.     protected int getTabSize() {
  54.         Integer i = (Integer) getDocument().getProperty(PlainDocument.tabSizeAttribute);
  55.         int size = (i != null) ? i.intValue() : 8;
  56.         return size;
  57.     }
  58.  
  59.     /**
  60.      * Returns the max number of characters per line set for the document,
  61.      * If none is set, it is computed by examining all lines to find the
  62.      * longest line.
  63.      *
  64.      * @return the max number
  65.      */
  66.     protected int getLineLimit() {
  67.         Integer lineLimit = (Integer) getDocument().getProperty(PlainDocument.lineLimitAttribute);
  68.         if(lineLimit == null) {
  69.             int width = 0;
  70.  
  71.             int totalLines = getElement().getElementCount();
  72.             for(int i = 0; i < totalLines; i++) {
  73.                 Element line = getElement().getElement(i);
  74.                 int p0 = line.getStartOffset();
  75.                 int p1 = line.getEndOffset();
  76.                 if(p1 - p0 > width) {
  77.                     width = p1 - p0;
  78.                 }
  79.             }
  80.             lineLimit = new Integer(width);
  81.             getDocument().putProperty(PlainDocument.lineLimitAttribute, lineLimit);
  82.         }
  83.         return lineLimit.intValue();
  84.     }
  85.  
  86.     /**
  87.      * Renders a line of text, suppressing whitespace at the end
  88.      * and exanding any tabs.  This is implemented to make calls
  89.      * to the methods <code>drawUnselectedText</code> and 
  90.      * <code>drawSelectedText</code> so that the way selected and 
  91.      * unselected text are rendered can be customized.
  92.      *
  93.      * @param lineIndex the line to draw >= 0
  94.      * @param g the graphics context
  95.      * @param x the starting X position >= 0
  96.      * @param y the starting Y position >= 0
  97.      * @see #drawUnselectedText
  98.      * @see #drawSelectedText
  99.      */
  100.     protected void drawLine(int lineIndex, Graphics g, int x, int y) {
  101.         try {
  102.             Element line = getElement().getElement(lineIndex);
  103.             int p0 = line.getStartOffset();
  104.             int p1 = line.getEndOffset();
  105.             p1 = Math.min(getDocument().getLength(), p1);
  106.             if (sel0 == sel1) {
  107.                 // no selection
  108.                 drawUnselectedText(g, x, y, p0, p1);
  109.             } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) {
  110.                 drawSelectedText(g, x, y, p0, p1);
  111.             } else if (sel0 >= p0 && sel0 <= p1) {
  112.                 if (sel1 >= p0 && sel1 <= p1) {
  113.                     x = drawUnselectedText(g, x, y, p0, sel0);
  114.                     x = drawSelectedText(g, x, y, sel0, sel1);
  115.                     drawUnselectedText(g, x, y, sel1, p1);
  116.                 } else {
  117.                     x = drawUnselectedText(g, x, y, p0, sel0);
  118.                     drawSelectedText(g, x, y, sel0, p1);
  119.                 }
  120.             } else if (sel1 >= p0 && sel1 <= p1) {
  121.                 x = drawSelectedText(g, x, y, p0, sel1);
  122.                 drawUnselectedText(g, x, y, sel1, p1);
  123.             } else {
  124.                 drawUnselectedText(g, x, y, p0, p1);
  125.             }
  126.         } catch (BadLocationException e) {
  127.             throw new StateInvariantError("Can't render line: " + lineIndex);
  128.         }
  129.     }
  130.  
  131.     /**
  132.      * Renders the given range in the model as normal unselected
  133.      * text.  Uses the foreground or disabled color to render the text.
  134.      *
  135.      * @param g the graphics context
  136.      * @param x the starting X coordinate >= 0
  137.      * @param y the starting Y coordinate >= 0
  138.      * @param p0 the beginning position in the model >= 0
  139.      * @param p1 the ending position in the model >= 0
  140.      * @returns the X location of the end of the range >= 0
  141.      * @exception BadLocationException if the range is invalid
  142.      */
  143.     protected int drawUnselectedText(Graphics g, int x, int y, 
  144.                                      int p0, int p1) throws BadLocationException {
  145.         g.setColor(unselected);
  146.         Document doc = getDocument();
  147.         doc.getText(p0, p1 - p0, lineBuffer);
  148.         return Utilities.drawTabbedText(lineBuffer, x, y, g, this, p0);
  149.     }
  150.  
  151.     /**
  152.      * Renders the given range in the model as selected text.  This
  153.      * is implemented to render the text in the color specified in
  154.      * the hosting component.  It assumes the highlighter will render
  155.      * the selected background.
  156.      *
  157.      * @param g the graphics context
  158.      * @param x the starting X coordinate >= 0
  159.      * @param y the starting Y coordinate >= 0
  160.      * @param p0 the beginning position in the model >= 0
  161.      * @param p1 the ending position in the model >= 0
  162.      * @returns the location of the end of the range.
  163.      * @exception BadLocationException if the range is invalid
  164.      */
  165.     protected int drawSelectedText(Graphics g, int x, 
  166.                                    int y, int p0, int p1) throws BadLocationException {
  167.         g.setColor(selected);
  168.         Document doc = getDocument();
  169.         doc.getText(p0, p1 - p0, lineBuffer);
  170.         return Utilities.drawTabbedText(lineBuffer, x, y, g, this, p0);
  171.     }
  172.  
  173.     /**
  174.      * Gives access to a buffer that can be used to fetch 
  175.      * text from the associated document.
  176.      *
  177.      * @returns the buffer
  178.      */
  179.     protected final Segment getLineBuffer() {
  180.         return lineBuffer;
  181.     }
  182.  
  183.     final void updateMetrics() {
  184.     Component host = getContainer();
  185.     Font f = host.getFont();
  186.     metrics = host.getFontMetrics(f);
  187.     int columnWidth = metrics.charWidth('m');
  188.     width = getLineLimit() * columnWidth;
  189.     tabSize = getTabSize() * columnWidth;
  190.     }
  191.  
  192.     // ---- View methods ----------------------------------------------------
  193.  
  194.     /**
  195.      * Sets the parent of the view.
  196.      * The parent calls this on the child to tell it who its
  197.      * parent is.  If this is null, the view has been removed
  198.      * and we need to remove the associated component from its
  199.      * parent.  This is used here to determine what the hosting
  200.      * component is.
  201.      *
  202.      * @param p the parent view
  203.      */
  204.     public void setParent(View p) {
  205.         super.setParent(p);
  206.         host = (JTextComponent) getContainer();
  207.     }
  208.  
  209.     /**
  210.      * Determines the preferred span for this view along an
  211.      * axis.
  212.      *
  213.      * @param axis may be either View.X_AXIS or View.Y_AXIS
  214.      * @returns  the span the view would like to be rendered into >= 0.
  215.      *           Typically the view is told to render into the span
  216.      *           that is returned, although there is no guarantee.  
  217.      *           The parent may choose to resize or break the view.
  218.      * @exception IllegalArgumentException for an invalid axis
  219.      */
  220.     public float getPreferredSpan(int axis) {
  221.     updateMetrics();
  222.         switch (axis) {
  223.         case View.X_AXIS:
  224.             return width;
  225.         case View.Y_AXIS:
  226.             return getElement().getElementCount() * metrics.getHeight();
  227.         default:
  228.             throw new IllegalArgumentException("Invalid axis: " + axis);
  229.         }
  230.     }
  231.  
  232.     /**
  233.      * Renders using the given rendering surface and area on that surface.
  234.      * The view may need to do layout and create child views to enable
  235.      * itself to render into the given allocation.
  236.      *
  237.      * @param g the rendering surface to use
  238.      * @param a the allocated region to render into
  239.      *
  240.      * @see View#paint
  241.      */
  242.     public void paint(Graphics g, Shape a) {
  243.         Rectangle alloc = (Rectangle) a;
  244.         tabBase = alloc.x;
  245.         g.setFont(host.getFont());
  246.         sel0 = host.getSelectionStart();
  247.         sel1 = host.getSelectionEnd();
  248.         unselected = (host.isEnabled()) ? 
  249.             host.getForeground() : host.getDisabledTextColor();
  250.     Caret c = host.getCaret();
  251.         selected = c.isSelectionVisible() ? host.getSelectedTextColor() : unselected;
  252.     updateMetrics();
  253.  
  254.         // If the lines are clipped then we don't expend the effort to
  255.         // try and paint them.  Since all of the lines are the same height
  256.         // with this object, determination of what lines need to be repainted
  257.         // is quick.
  258.         Rectangle clip = g.getClipBounds();
  259.         int fontHeight = metrics.getHeight();
  260.         int heightBelow = (alloc.y + alloc.height) - (clip.y + clip.height);
  261.         int linesBelow = Math.max(0, heightBelow / fontHeight);
  262.         int heightAbove = clip.y - alloc.y;
  263.         int linesAbove = Math.max(0, heightAbove / fontHeight);
  264.         int linesTotal = alloc.height / fontHeight;
  265.  
  266.         // update the visible lines
  267.         Rectangle lineArea = lineToRect(a, linesAbove);
  268.         int y = lineArea.y + metrics.getAscent();
  269.         int x = lineArea.x;
  270.         Element map = getElement();
  271.         int endLine = Math.min(map.getElementCount(), linesTotal - linesBelow);
  272.         for (int line = linesAbove; line < endLine; line++) {
  273.             drawLine(line, g, x, y);
  274.             y += fontHeight;
  275.         }
  276.     }
  277.  
  278.     /**
  279.      * Signals that the desired span has changed. 
  280.      *
  281.      * @param child the child view
  282.      * @param width true if the width preference has changed
  283.      * @param height true if the height preference has changed
  284.      * @see com.sun.java.swing.JComponent#revalidate
  285.      */
  286.     public void preferenceChanged(View child, boolean width, boolean height) {
  287.     getDocument().putProperty(PlainDocument.lineLimitAttribute, null);       
  288.     super.preferenceChanged(child, width, height);
  289.     }
  290.  
  291.     /**
  292.      * Provides a mapping from the document model coordinate space
  293.      * to the coordinate space of the view mapped to it.
  294.      *
  295.      * @param pos the position to convert >= 0
  296.      * @param a the allocated region to render into
  297.      * @return the bounding box of the given position
  298.      * @exception BadLocationException  if the given position does not
  299.      *   represent a valid location in the associated document
  300.      * @see View#modelToView
  301.      */
  302.     public Shape modelToView(int pos, Shape a) throws BadLocationException {
  303.         // line coordinates
  304.         Document doc = getDocument();
  305.         Element map = getElement();
  306.         int lineIndex = map.getElementIndex(pos);
  307.         Rectangle lineArea = lineToRect(a, lineIndex);
  308.         
  309.         // determine span from the start of the line
  310.         tabBase = lineArea.x;
  311.         Element line = map.getElement(lineIndex);
  312.         int p0 = line.getStartOffset();
  313.         doc.getText(p0, pos - p0, lineBuffer);
  314.         int xOffs = Utilities.getTabbedTextWidth(lineBuffer, metrics, tabBase, this, p0);
  315.  
  316.         // fill in the results and return
  317.         lineArea.x += xOffs;
  318.         lineArea.width = 1;
  319.         lineArea.height = metrics.getHeight();
  320.         return lineArea;
  321.     }
  322.  
  323.     /**
  324.      * Provides a mapping from the view coordinate space to the logical
  325.      * coordinate space of the model.
  326.      *
  327.      * @param fx the X coordinate >= 0
  328.      * @param fy the Y coordinate >= 0
  329.      * @param a the allocated region to render into
  330.      * @return the location within the model that best represents the
  331.      *  given point in the view >= 0
  332.      * @see View#viewToModel
  333.      */
  334.     public int viewToModel(float fx, float fy, Shape a) {
  335.         Rectangle alloc = a.getBounds();
  336.         Document doc = getDocument();
  337.         int x = (int) fx;
  338.         int y = (int) fy;
  339.         if (y < alloc.y) {
  340.             // above the area covered by this icon, so the the position
  341.             // is assumed to be the start of the coverage for this view.
  342.             return getStartOffset();
  343.         } else if (y > alloc.y + alloc.height) {
  344.             // below the area covered by this icon, so the the position
  345.             // is assumed to be the end of the coverage for this view.
  346.             return getEndOffset() - 1;
  347.         } else {
  348.             // positioned within the coverage of this view vertically,
  349.             // so we figure out which line the point corresponds to.
  350.             // if the line is greater than the number of lines contained, then
  351.             // simply use the last line as it represents the last possible place
  352.             // we can position to.
  353.             Element map = doc.getDefaultRootElement();
  354.             int lineIndex = Math.abs((y - alloc.y) / metrics.getHeight() );
  355.             if (lineIndex >= map.getElementCount()) {
  356.                 return getEndOffset() - 1;
  357.             }
  358.             Element line = map.getElement(lineIndex);
  359.             if (x < alloc.x) {
  360.                 // point is to the left of the line
  361.                 return line.getStartOffset();
  362.             } else if (x > alloc.x + alloc.width) {
  363.                 // point is to the right of the line
  364.                 return line.getEndOffset() - 1;
  365.             } else {
  366.                 // Determine the offset into the text
  367.                 try {
  368.                     int p0 = line.getStartOffset();
  369.                     int p1 = line.getEndOffset() - 1;
  370.                     doc.getText(p0, p1 - p0, lineBuffer);
  371.                     tabBase = alloc.x;
  372.                     int offs = p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics,
  373.                                                                   tabBase, x, this, p0);
  374.                     return offs;
  375.                 } catch (BadLocationException e) {
  376.                     // should not happen
  377.                     return -1;
  378.                 }
  379.             }
  380.         }
  381.     }    
  382.  
  383.     /**
  384.      * Gives notification that something was inserted into the document
  385.      * in a location that this view is responsible for.
  386.      *
  387.      * @param changes the change information from the associated document
  388.      * @param a the current allocation of the view
  389.      * @param f the factory to use to rebuild if the view has children
  390.      * @see View#insertUpdate
  391.      */
  392.     public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  393.     updateDamage(changes, a, f);
  394.     }
  395.  
  396.     /**
  397.      * Gives notification that something was removed from the document
  398.      * in a location that this view is responsible for.
  399.      *
  400.      * @param changes the change information from the associated document
  401.      * @param a the current allocation of the view
  402.      * @param f the factory to use to rebuild if the view has children
  403.      * @see View#removeUpdate
  404.      */
  405.     public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  406.         updateDamage(changes, a, f);
  407.     }
  408.  
  409.     /**
  410.      * Gives notification from the document that attributes were changed
  411.      * in a location that this view is responsible for.
  412.      *
  413.      * @param changes the change information from the associated document
  414.      * @param a the current allocation of the view
  415.      * @param f the factory to use to rebuild if the view has children
  416.      * @see View#changedUpdate
  417.      */
  418.     public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  419.         updateDamage(changes, a, f);
  420.     }
  421.  
  422.     // --- TabExpander methods ------------------------------------------
  423.  
  424.     /**
  425.      * Returns the next tab stop position after a given reference position.
  426.      * This implementation does not support things like centering so it
  427.      * ignores the tabOffset argument.
  428.      *
  429.      * @param x the current position >= 0
  430.      * @param tabOffset the position within the text stream
  431.      *   that the tab occurred at >= 0.
  432.      * @return the tab stop, measured in points >= 0
  433.      */
  434.     public float nextTabStop(float x, int tabOffset) {
  435.         int ntabs = (((int) x) - tabBase) / tabSize;
  436.         return tabBase + ((ntabs + 1) * tabSize);
  437.     }
  438.  
  439.     
  440.     // --- local methods ------------------------------------------------
  441.  
  442.     /* 
  443.      * We can damage the line that begins the range to cover
  444.      * the case when the insert is only on one line.  If lines
  445.      * are added or removed we will damage the whole view.
  446.      */
  447.     void updateDamage(DocumentEvent changes, Shape a, ViewFactory f) {
  448.         if (host.isShowing()) {
  449.         updateMetrics();
  450.             Element elem = getElement();
  451.             DocumentEvent.ElementChange ec = changes.getChange(elem);
  452.             
  453.             Element[] added = (ec != null) ? ec.getChildrenAdded() : null;
  454.             Element[] removed = (ec != null) ? ec.getChildrenRemoved() : null;
  455.             if (((added != null) && (added.length > 0)) || 
  456.                 ((removed != null) && (removed.length > 0))) {
  457.                 preferenceChanged(null, true, true);
  458.                 host.repaint();
  459.             } else {
  460.                 preferenceChanged(null, true, false);
  461.                 Element map = getElement();
  462.                 int line = map.getElementIndex(changes.getOffset());
  463.                 damageLineRange(line, line, a, host);
  464.             }
  465.         }
  466.     }
  467.  
  468.     private void damageLineRange(int line0, int line1, Shape a, Component host) {
  469.         if (a != null) {
  470.             Rectangle area0 = lineToRect(a, line0);
  471.             Rectangle area1 = lineToRect(a, line1);
  472.             if ((area0 != null) && (area1 != null)) {
  473.                 Rectangle damage = area0.union(area1);
  474.                 host.repaint(damage.x, damage.y, damage.width, damage.height);
  475.             } else {
  476.                 host.repaint();
  477.             }
  478.         }
  479.     }
  480.  
  481.     private Rectangle lineToRect(Shape a, int line) {
  482.         Rectangle r = null;
  483.         if (metrics != null) {
  484.             Rectangle alloc = a.getBounds();
  485.             r = new Rectangle(alloc.x, alloc.y + (line * metrics.getHeight()),
  486.                               alloc.width, metrics.getHeight());
  487.         }
  488.         return r;
  489.     }
  490.  
  491.     // --- member variables -----------------------------------------------
  492.  
  493.     /**
  494.      * Font metrics for the currrent font.
  495.      */
  496.     protected FontMetrics metrics;
  497.  
  498.     Segment lineBuffer;
  499.     int width;
  500.     int tabSize;
  501.     int tabBase;
  502.     JTextComponent host;
  503.     
  504.     int sel0;
  505.     int sel1;
  506.     Color unselected;
  507.     Color selected;
  508.     
  509. }
  510.